// 版权归Go作者所有。保留所有权利。
// 此源代码的使用受BSD样式
// 许可证的约束，该许可证可以在许可证文件中找到。

// go:build aix | |达尔文| | |蜻蜓| | freebsd | | linux | | netbsd | openbsd | | solaris 

package net

import (
	"internal/bytealg"
	"internal/godebug"
	"os"
	"runtime"
	"sync"
	"syscall"
)

// conf。
type conf struct {
	// forceCgoLookupHost强制始终使用CGO（如果可用）。
	forceCgoLookupHost bool

	netGo  bool // go DNS解析强制
	netCgo bool // cgo DNS解析强制

	// 机器具有/etc/mdns。允许文件
	hasMDNSAllow bool

	goos          string // 运行时。好的，为了方便测试
	dnsDebugLevel int

	nss    *nssConf
	resolv *dnsConfig
}

var (
	confOnce sync.Once // 通过initConfVal 
	confVal  = &conf{goos: runtime.GOOS}
)

// 系统配置返回机器的网络配置。如果程序试图执行自己的DNS请求，达尔文会弹出恼人的对话框。所以一定要用cgo来代替，这是
func systemConf() *conf {
	confOnce.Do(initConfVal)
	return confVal
}

func initConfVal() {
	dnsMode, debugLevel := goDebugNetDNS()
	confVal.dnsDebugLevel = debugLevel
	confVal.netGo = netGo || dnsMode == "go"
	confVal.netCgo = netCgo || dnsMode == "cgo"

	if confVal.dnsDebugLevel > 0 {
		defer func() {
			switch {
			case confVal.netGo:
				if netGo {
					println("go package net: built with netgo build tag; using Go's DNS resolver")
				} else {
					println("go package net: GODEBUG setting forcing use of Go's resolver")
				}
			case confVal.forceCgoLookupHost:
				println("go package net: using cgo DNS resolver")
			default:
				println("go package net: dynamic selection of DNS resolver")
			}
		}()
	}

	// 避免的。
	if runtime.GOOS == "darwin" || runtime.GOOS == "ios" {
		confVal.forceCgoLookupHost = true
		return
	}

	// 如果指定了任何环境指定的解析器选项，
	// 强制cgo。请注意，通过使用空字符串指定，LOCALDOMAIN只能改变行为
	// 。
	_, localDomainDefined := syscall.Getenv("LOCALDOMAIN")
	if os.Getenv("RES_OPTIONS") != "" ||
		os.Getenv("HOSTALIASES") != "" ||
		confVal.netCgo ||
		localDomainDefined {
		confVal.forceCgoLookupHost = true
		return
	}

	// OpenBSD显然可以让你覆盖resolv的位置。配置
	// 带有ASR_配置。如果我们注意到了这一点，请遵从libc。
	if runtime.GOOS == "openbsd" && os.Getenv("ASR_CONFIG") != "" {
		confVal.forceCgoLookupHost = true
		return
	}

	if runtime.GOOS != "openbsd" {
		confVal.nss = parseNSSConfFile("/etc/nsswitch.conf")
	}

	confVal.resolv = dnsReadConfig("/etc/resolv.conf")
	if confVal.resolv.err != nil && !os.IsNotExist(confVal.resolv.err) &&
		!os.IsPermission(confVal.resolv.err) {
		// 如果我们看不懂决议。conf文件，假设它
		// 里面有重要的东西，请听从cgo。
		// libc的解析器可能也会失败，但至少这不是我们的错。
		confVal.forceCgoLookupHost = true
	}

	if _, err := os.Stat("/etc/mdns.allow"); err == nil {
		confVal.hasMDNSAllow = true
	}
}

// canUseCgo报告是否允许调用cgo函数
// 用于非主机名查找。
func (c *conf) canUseCgo() bool {
	return c.hostLookupOrder(nil, "") == hostLookupCgo
}

// hostLookupOrder决定使用哪种策略解析主机名。
// 提供的解析器是可选的。NIL意味着不考虑它的选择。
func (c *conf) hostLookupOrder(r *Resolver, hostname string) (ret hostLookupOrder) {
	if c.dnsDebugLevel > 1 {
		defer func() {
			print("go package net: hostLookupOrder(", hostname, ") = ", ret.String(), "\n")
		}()
	}
	fallbackOrder := hostLookupCgo
	if c.netGo || r.preferGo() {
		fallbackOrder = hostLookupFilesDNS
	}
	if c.forceCgoLookupHost || c.resolv.unknownOpt || c.goos == "android" {
		return fallbackOrder
	}
	if bytealg.IndexByteString(hostname, '\\') != -1 || bytealg.IndexByteString(hostname, '%') != -1 {
		// 不要处理带有反斜杠的特殊格式主机名
		// 或“%”。
		return fallbackOrder
	}

	// OpenBSD是唯一的，不使用nsswitch。conf.
	// 它也不支持MDN。
	if c.goos == "openbsd" {
		// OpenBSD的解决方案。conf manpage说一个不存在的
		// resolv。conf表示“查找”默认为仅“文件”，
		// 不带DNS查找。
		if os.IsNotExist(c.resolv.err) {
			return hostLookupFiles
		}
		lookup := c.resolv.lookup
		if len(lookup) == 0 {
			// https:
			// “如果在
			// 系统的resolv.conf文件中未使用查找关键字，则假定的
			// 顺序为‘bind file’，”
			return hostLookupDNSFiles
		}
		if len(lookup) < 1 || len(lookup) > 2 {
			return fallbackOrder
		}
		switch lookup[0] {
		case "bind":
			if len(lookup) == 2 {
				if lookup[1] == "file" {
					return hostLookupDNSFiles
				}
				return fallbackOrder
			}
			return hostLookupDNS
		case "file":
			if len(lookup) == 2 {
				if lookup[1] == "bind" {
					return hostLookupFilesDNS
				}
				return fallbackOrder
			}
			return hostLookupFiles
		default:
			return fallbackOrder
		}
	}

	// 通过删除任何尾随点来规范化主机名。
	if stringsHasSuffix(hostname, ".") {
		hostname = hostname[:len(hostname)-1]
	}
	if stringsHasSuffixFold(hostname, ".local") {
		// 根据RFC 6762，“.local”TLD是特殊的。而
		// 因为Go的本机解析器不执行MDN或
		// 类似的本地解析机制，所以假设
		// libc可能（通过Avahi等）使用cgo。
		return fallbackOrder
	}

	nss := c.nss
	srcs := nss.sources["hosts"]
	// If/etc/nsswitch。conf不存在或没有为“主机”指定任何
	// 源，假设Go的DNS可以正常工作。
	if os.IsNotExist(nss.err) || (nss.err == nil && len(srcs) == 0) {
		if c.goos == "solaris" {
			// illumos默认为“nis[NOTFOUND=return]files”
			return fallbackOrder
		}
		return hostLookupFilesDNS
	}
	if nss.err != nil {
		// 我们无法解析或打开nsswitch。conf，所以保守地假设如果cgo是
		// 可用的，我们应该使用cgo。
		return fallbackOrder
	}

	var mdnsSource, filesSource, dnsSource bool
	var first string
	for _, src := range srcs {
		if src.source == "myhostname" {
			if isLocalhost(hostname) || isGateway(hostname) {
				return fallbackOrder
			}
			hn, err := getHostname()
			if err != nil || stringsEqualFold(hostname, hn) {
				return fallbackOrder
			}
			continue
		}
		if src.source == "files" || src.source == "dns" {
			if !src.standardCriteria() {
				return fallbackOrder // 非标准；让libc来处理吧。
			}
			if src.source == "files" {
				filesSource = true
			} else if src.source == "dns" {
				dnsSource = true
			}
			if first == "" {
				first = src.source
			}
			continue
		}
		if stringsHasPrefix(src.source, "mdns") {
			// 例如“mdns4”、“mdns4_minimal”
			// 如果是*，我们之前已经返回true。地方的
			// libc无论如何也不会找到成功的机会。
			mdnsSource = true
			continue
		}
		// 一些我们不知道如何处理的消息来源。
		return fallbackOrder
	}

	// 我们不解析MDN。允许文件。它们很罕见。如果存在一个
	// ，它可能会列出其他TLD（除了.local），甚至
	// '*'，所以就让libc来处理它吧。
	if mdnsSource && c.hasMDNSAllow {
		return fallbackOrder
	}

	// Go可以在没有cgo和C线程的情况下处理它的情况
	// 开销。
	switch {
	case filesSource && dnsSource:
		if first == "files" {
			return hostLookupFilesDNS
		} else {
			return hostLookupDNSFiles
		}
	case filesSource:
		return hostLookupFiles
	case dnsSource:
		return hostLookupDNS
	}

	// 有点奇怪。让libc来处理吧。
	return fallbackOrder
}

// goDebugNetDNS解析GODEBUG“netdns”值的值。
// netdns值的形式可以是：
// 1
// 2
// cgo
// go使用go进行DNS查找
// cgo+1
// cgo+2
// 等。
func goDebugNetDNS() (dnsMode string, debugLevel int) {
	goDebug := godebug.Get("netdns")
	parsePart := func(s string) {
		if s == "" {
			return
		}
		if '0' <= s[0] && s[0] <= '9' {
			debugLevel, _, _ = dtoi(s)
		} else {
			dnsMode = s
		}
	}
	if i := bytealg.IndexByteString(goDebug, '+'); i != -1 {
		parsePart(goDebug[:i])
		parsePart(goDebug[i+1:])
		return
	}
	parsePart(goDebug)
	return
}

// isLocalhost报告是否应将h视为myhostname NSS模块的“本地主机”
// 名称。
func isLocalhost(h string) bool {
	return stringsEqualFold(h, "localhost") || stringsEqualFold(h, "localhost.localdomain") || stringsHasSuffixFold(h, ".localhost") || stringsHasSuffixFold(h, ".localhost.localdomain")
}

// isGateway报告是否应将h视为“网关”
// myhostname NSS模块的名称。
func isGateway(h string) bool {
	return stringsEqualFold(h, "gateway")
}
